home *** CD-ROM | disk | FTP | other *** search
- /* CleanPICT.c */
- /*
- * Copyright © 1989, 1990 Martin Minow and MacTutor.
- *
- * You may use this software in any application and
- * redistribute this source without restriction as long
- * as this copyright and permission notice remains intact
- * and the source is not redistributed for profit and you
- * assume all responsibility for the proper operation of
- * this software.
- *
- * Written in Think C. Set Tabs every 2 characters.
- */
-
- /*
- * Clean the picture by searching for noise blobs. A
- * noise blob is defined as an "island" of one color
- * (forground or background) that is completly enclosed
- * by the other color and is smaller than a threshold
- * value.
- *
- * The algorithm uses a recursive "flooding" algorithm
- * (also called a "maze walking" procedure) that uses
- * several local variables. These are defined as global
- * (file local) -- a "real" implementation would define
- * them in a structure that is allocated by the top-level
- * function and passed as a parameter to the worker bees.
- *
- * At any point in the process, the do_point function
- * examines the north/south/east/west neighbors of the
- * current point (which is known to be in the island
- * color). If the point is not part of the island,
- * or already seen, the function returns TRUE to continue
- * looking. Otherwise, this new island point is marked
- * "seen" and the threshold counter incremented. If
- * the counter exceeds threshold, the function returns
- * FALSE and the cleanout fails for this point: the
- * island is too big.
- *
- * The process concludes when any subroutine invocation
- * returns FALSE (not an island), or when only background
- * points remain. A long, thin structure that extends
- * beyond the edge of the seen array or outside the PICT is
- * not an island.
- *
- * Note: this function is disgustingly unoptional and
- * very slow (about 1 second per row on a Mac-SE for
- * an 1800 pixel wide image). I'm sure that a half-hour
- * reading any decent graphics textbook would improve the
- * code, but it only had to run once so why bother?
- * Remember Gordon Bell's Law:
- * "Sometimes it's better to have 20 million instructions
- * by Friday than 20 million instructions per second."
- * It's about twice as fast now, thanks to some good
- * ideas from Mike Morton.
- */
-
- #include "CleanPICT.h"
- /*
- * These two parameters were used to check performance
- * improvements:
- * USE_TOOLBOX, if defined non-zero, uses the Macintosh
- * bit-manipulation functions. If zero, CleanPict
- * uses local routines.
- * UPDATE_MAX determines how frequently changes are
- * written to the screen (1 updates each change as
- * it happens, 10 updates every tenth change, etc.).
- */
- #define USE_TOOLBOX 0
- #define UPDATE_MAX 10
-
- /*
- * Some interesting stuff in the current document -- this
- * just saves lots of typing.
- */
- #define PICT (DOC.pictPort->portBits)
- #define RECT (PICT.bounds)
-
- #define XMID (XMAX / 2) /* Offset to center pixel */
- #define YMID (YMAX / 2) /* Offset to center pixel */
- /*
- * The seen bit-array records the shape of the current
- * island. This keeps the recursive island search
- * routine from examining the same island point twice,
- * and it marks the island for later erasure. It is
- * written in this strange manner so we can use the
- * compiler's structure assignment capability to clear
- * it: this should be faster than a subroutine call to
- * memset(),
- */
- typedef struct {
- Ulong v[XMAX * YMAX / sizeof (Ulong) + 1];
- } SeenRecord;
- SeenRecord seen_map; /* This is the active map */
- static SeenRecord empty_map; /* This clears seen_map */
-
- /*
- * Make sure [x][y] are in range. By adding <X,Y>MID and
- * casting the value to unsigned, we can save on one
- * comparison.
- */
- #define in_seen_map(x, y) ( \
- (((unsigned) ((x) + XMID)) < (2 * XMID)) \
- && (((unsigned) ((y) + YMID)) < (2 * YMID)) \
- )
- /*
- * XY() converts offsets from the current pixel into an
- * index into the seen map bit-vector. The algorithm is
- * ((y + YMID) * XMAX) + (x + XMID)
- * Since XMAX is a small, constant, power of two, the
- * compiler should replace it by a left-shift. We
- * do this explicitly just in case.
- */
- #define XY(x, y) ( \
- (((y) + YMID) << LOG_XMAX) \
- + ((x) + XMID) \
- )
- /*
- * These functions manipulate the seen map. The program
- * uses a single seen map for the entire process, stored
- * in a global vector. This would work even if the user
- * has multiple pictures open, as the application will
- * switch between pictures only after fully-processing
- * a pixel.
- */
- #if USE_TOOLBOX
- #define myBitSet(i) BitSet(seen_map.v, (i))
- #define myBitClr(i) BitClr(seen_map.v, (i))
- #define myBitTst(i) BitTst(seen_map.v, (i))
- #else
- static void myBitSet(Ulong);
- static void myBitClr(Ulong);
- static Boolean myBitTst(Ulong);
- #endif
- #define set_seen(x, y) myBitSet(XY(x, y))
- #define clr_seen(x, y) myBitClr(XY(x, y))
- #define is_seen(x, y) ( \
- in_seen_map(x, y) && myBitTst(XY(x, y)) \
- )
-
- /*
- * The BitOp macro is used to mung the PICT bitmap.
- * Note that x and y are cast to unsigned longs.
- */
- #define BitOp(op, x, y) ( \
- op( \
- PICT.baseAddr + ((Ulong) y) * PICT.rowBytes, \
- ((Ulong) x) \
- ) \
- )
-
- #define Pixel(x, y) BitOp(BitTst, x, y)
-
- Boolean do_pixel(WindowPtr, int, int);
- void invert_seen_map(WindowPtr);
- Boolean ok_point(WindowPtr, Ulong, Ulong);
-
- extern Ulong VIA_ticks(void);
-
- /*
- * Clean out the picture. Note: the boarder pixels
- * aren't cleaned.
- */
- void
- clean_picture(window, quit)
- WindowPtr window;
- Boolean quit;
- {
- Boolean this;
- Ulong now;
-
- switch (DOC.state) {
- case Idle:
- return;
- case Init:
- /*
- * This is a new cleanup operation. Start from
- * the top-left of the document.
- */
- GetDateTime(&DOC.start);
- DOC.elapsed = 0;
- DOC.examined = DOC.found = 0;
- DOC.updateCount = 0;
- DOC.updateRgn = NewRgn();
- DOC.thisUpdateRgn = NewRgn();
- DOC.bottom = RECT.bottom - 1;
- DOC.right = RECT.right - 1;
- DOC.center.v = RECT.top + 1;
- DOC.center.h = RECT.left + 1;
- DOC.state = Working;
- DOC.dirty = TRUE;
- break;
- case Working:
- if (quit || DOC.center.h >= DOC.right) {
- if (quit || ++DOC.center.v >= DOC.bottom) {
- finish: if (DOC.updateCount > 0) {
- InvalRgn(DOC.updateRgn);
- DisposeRgn(DOC.updateRgn);
- DisposeRgn(DOC.thisUpdateRgn);
- }
- GetDateTime(&now);
- DOC.elapsed = now - DOC.start;
- SetWTitle(window, DOC.fileName);
- ARROW_CURSOR;
- SysBeep(10);
- DOC.state = Idle;
- make_about_window(window);
- quit = FALSE;
- break;
- }
- DOC.center.h = RECT.left + 1;
- DOC.island = Pixel(RECT.left, DOC.center.v);
- }
- /*
- * Within a row, we skip over a run of the
- * current pixel value, then check the pixel
- * just above this one: if it's the same flavor,
- * we examined this pixel when we did a previous
- * row and decided it couldn't be in an island.
- */
- while (DOC.center.h < DOC.right) {
- this = Pixel(DOC.center.h, DOC.center.v);
- if (this != DOC.island)
- goto possible_island;
- ++DOC.center.h;
- }
- break;
- possible_island:
- ++DOC.examined; /* Island here? */
- DOC.count = 0; /* No pixels yet */
- seen_map = empty_map; /* Clear seen map */
- DOC.island = this; /* Island's color */
- if (do_pixel(window, 0, 0)) { /* Here we go! */
- invert_seen_map(window); /* Zap the island */
- ++DOC.found;
- /*
- * The current pixel type has changed!
- */
- DOC.island = Pixel(DOC.center.h, DOC.center.v);
- }
- ++DOC.center.h;
- break;
- }
- }
-
- /*
- * This is the recursive routine that does all the
- * work. It looks at the four cardinal neighbors:
- * if seen: continue.
- * else if off the edge of the seen map: return FALSE;
- * else if marked, check whether threshold exceeded.
- * if so, return FALSE,
- * else, call do_pixel for the cardinal point:
- * if it returns false, return FALSE.
- * else (not the same color or all cardinal points
- * already seen) return TRUE.
- * Thus: any invocation of do_pixel that returns FALSE
- * stops the process.
- */
- Boolean
- do_pixel(window, x, y)
- WindowPtr window;
- register int x;
- register int y;
- {
- Point thePoint;
-
- if (in_seen_map(x, y) == FALSE)
- return (FALSE); /* Fell off the edge */
- if (is_seen(x, y)) /* Have we been here? */
- return (TRUE); /* Don't do it twice */
- /*
- * This will need work if the PICT cannot be bounded
- * by a normal Mac Rect.
- */
- thePoint.h = x + DOC.center.h;
- thePoint.v = y + DOC.center.v;
- if (PtInRect(thePoint, &RECT) == FALSE)
- return (TRUE); /* Fell off the pict */
- else if (Pixel(thePoint.h, thePoint.v) != DOC.island)
- return (TRUE); /* It's in the background */
- else {
- set_seen(x, y); /* This is in the island */
- if (++DOC.count > DOC.threshold)
- return (FALSE); /* Island is too big */
- else if (do_pixel(window, x + 1, y )
- && do_pixel(window, x, y + 1)
- && do_pixel(window, x - 1, y )
- && do_pixel(window, x, y - 1))
- return (TRUE); /* Still in the island */
- else {
- return (FALSE); /* Propogate failure */
- }
- }
- }
-
- /*
- * The seen map describes the island: just invert it
- * to clean out the island.
- */
- void
- invert_seen_map(window)
- WindowPtr window;
- {
- register int x, y;
- Rect box;
- Boolean seenTop = FALSE;
-
- for (y = -YMID; y < YMID; y++) {
- for (x = -XMID; x < YMID; x++) {
- if (is_seen(x, y)) {
- if (DOC.island)
- BitOp(
- BitClr, x + DOC.center.h, y + DOC.center.v);
- else {
- BitOp(
- BitSet, x + DOC.center.h, y + DOC.center.v);
- }
- box.right = x;
- box.bottom = y;
- if (seenTop == FALSE) {
- box.left = x;
- box.top = y;
- seenTop = TRUE;
- }
- }
- }
- }
- /*
- * Track the change on the screen image.
- */
- OffsetRect(&box, DOC.center.h, DOC.center.v);
- MapRect( /* Use window environment */
- &box, /* This is what changed */
- &RECT, /* PICT's boundaries */
- &window->portRect /* Map to these bounds */
- );
- InsetRect(&box, -1, -1); /* Cover the area */
- /*
- * If the update count is zero, set the update area
- * to this box. Otherwise, extend the update region
- * to include this island. If the threshold has
- * been reached, draw it onto the screen. Note that
- * we try to avoid the overhead of a full update
- * event. If this code changes, be sure to fix the
- * algorithm in the update event handler.
- */
- if (DOC.updateCount++ == 0)
- RectRgn(DOC.updateRgn, &box);
- else {
- RectRgn(DOC.thisUpdateRgn, &box);
- UnionRgn(
- DOC.thisUpdateRgn, DOC.updateRgn, DOC.updateRgn);
- }
- if (DOC.updateCount >= UPDATE_MAX) {
- CopyOSGrafPort(DOC.pictPort, window, DOC.updateRgn);
- DOC.updateCount = 0;
- }
- }
-
- #ifndef myBitSet
- /*
- * These functions are adapted from a suggestion by Mike
- * Morton: they implement bit set/clear/test without the
- * overhead of a toolbox call. Note that they could
- * easily be written in C for portability.
- */
-
- static Boolean
- myBitTst(bitNum)
- register Ulong bitNum;
- {
- asm {
- lea seen_map.v,A0 ; A0 -> seen map base
- move.w bitNum,D0 ; D0 := bit offset
- lsr.w #3,D0 ; D0 := byte offset
- add.w D0,A0 ; A0 -> correct byte
- not.b bitNum ; Flip bit numbering
- moveq #0,D0 ; Clear result word
- btst bitNum,(A0) ; Test the bit
- sne D0 ; Set D0 if not equal
- neg.b D0 ; flip result
- }
- /* Fall through to return result in D0 */
- }
-
- static void
- myBitSet(bitNum)
- register Ulong bitNum;
- {
- asm {
- lea seen_map.v,A0 ; A0 -> seen map base
- move.w bitNum,D0 ; D0 := bit offset
- lsr.w #3,D0 ; D0 := byte offset
- add.w D0,A0 ; A0 -> correct byte
- not.b bitNum ; Flip bit numbering
- bset bitNum,(A0) ; Set the bit
- }
- }
-
- static void
- myBitClr(bitNum)
- register Ulong bitNum;
- {
- asm {
- lea seen_map.v,A0 ; A0 -> seen map base
- move.w bitNum,D0 ; D0 := bit offset
- lsr.w #3,D0 ; D0 := byte offset
- add.w D0,A0 ; A0 -> correct byte
- not.b bitNum ; Flip bit numbering
- bclr bitNum,(A0) ; Set the bit
- }
- }
- #endif